package websocket2

import (
	"bytes"
	"github.com/satori/go.uuid"
	"log"
	"net/url"
	"sync"
	"time"

	"github.com/gorilla/websocket"
)

const (
	clientMsgChanCapacity = 500
)

/*客户端连接状态控制标志位*/
var (
	status    = make(chan bool, 1)
	netStatus = false
)

type Client struct {
	ip          string
	port        string
	wsHandleUrl string
	id          string
	name        string
	conn        *websocket.Conn
	closeChan   chan byte
	isClosed    bool
	lock        sync.Mutex
	timestamp   int64
	dataHandler func(*PacketData)
}

func NewClient(ip, port, wsHandleUrl string, clientName string, datahandler func(p *PacketData)) *Client {
	c := &Client{name: clientName}
	var (
		wsConn *websocket.Conn
		err    error
	)
	for {
		u := url.URL{Scheme: "ws", Host: ip + ":" + port, Path: wsHandleUrl}
		log.Printf("connecting websocket server 【%s】", u.String())
		if wsConn, _, err = websocket.DefaultDialer.Dial(u.String(), nil); err != nil {
			log.Println("connecting websocket err:", err)
		} else {
			uid, _ := uuid.NewV4()
			clientId := uid.String()
			c.id = clientId
			c.conn = wsConn
			c.closeChan = make(chan byte, 1)
			c.timestamp = time.Now().Unix()
			c.ip = ip
			c.port = port
			c.wsHandleUrl = wsHandleUrl
			c.dataHandler = datahandler
			log.Printf("websocket clientId:[%s] connected  successful......", clientId)
			msgDump := make(chan *PacketData, clientMsgChanCapacity)
			go c.read(msgDump)
			go c.handler(msgDump, datahandler)
			go c.heartCheck()
			break
		}
		time.Sleep(5 * time.Second)
	}
	return c
}
func (c *Client) Write(act int, data []byte) error {
	err := c.conn.WriteMessage(websocket.TextMessage, EnPacket(act, data))
	return err
}
func (c *Client) read(ch chan<- *PacketData) {
	var (
		data            []byte
		err             error
		maxRetryReadNum int
	)

	for {
		select {
		case <-c.closeChan:
			log.Printf("客户端连接:[%s]【read线程】退出......", c.id)
			return
		default:
			if _, data, err = c.conn.ReadMessage(); err != nil {
				log.Println("clientId:", c.id, "read message error:", err)
				if maxRetryReadNum > 2 {
					goto ERR
				}
				maxRetryReadNum++
				time.Sleep(500 * time.Millisecond)
				continue
			} else {
				if b := bytes.Equal(defaultHead, data[:4]); b {
					dataLen := byte2int(data[4:8]) //前4个字节是固定协议头 接着4个字节是数据(act+实际的数据)长度
					act, ret := Parse(data[8 : 8+dataLen])

					switch act {
					case ActRequestClientId:
						log.Println("收到服务端的actRequestClientId要求,返回clientId:", c.id)
						c.conn.WriteMessage(websocket.TextMessage, EnPacket(ActResponseClientId, []byte(c.id+","+c.name)))
					case ActRequestHeartBeat:
						c.conn.WriteMessage(websocket.TextMessage, EnPacket(ActResponseHeartBeat, ret))
						c.timestamp = int64(byte2int(ret))
					default:
						ch <- NewPacketData(act, ret)
					}
				}
			}
		}
	}
ERR:
	c.Close()
	log.Printf("客户端连接:[%s]【read线程】退出......", c.id)
}
func (c *Client) Close() {
	if c.conn == nil {
		return
	}
	//线程安全,可以重入的close(可多次执行)
	c.conn.Close()
	//通道的close只能执行一次 需要枷锁
	c.lock.Lock()
	if !c.isClosed {
		close(c.closeChan)
		c.isClosed = true
		log.Printf("客户端连接:[%s] 关闭成功,5秒重启连接......", c.id)
	}
	c.lock.Unlock()
	go c.reconnect()
}

func (c *Client) reconnect() {
	time.Sleep(5 * time.Second)
	var (
		wsConn *websocket.Conn
		err    error
	)
	u := url.URL{Scheme: "ws", Host: c.ip + ":" + c.port, Path: c.wsHandleUrl}
	log.Printf("reconnecting websocket server 【%s】", u.String())
	if wsConn, _, err = websocket.DefaultDialer.Dial(u.String(), nil); err != nil {
		log.Println("reconnecting websocket err:", err)
	} else {
		c.conn.Close()
		c.conn = wsConn
		uid, _ := uuid.NewV4()
		clientId := uid.String()
		c.id = clientId
		c.closeChan = make(chan byte, 1)
		c.timestamp = time.Now().Unix()
		c.isClosed = false
		log.Printf("websocket clientId:[%s] reconnected  successful......", clientId)
		msgDump := make(chan *PacketData, clientMsgChanCapacity)
		go c.read(msgDump)
		go c.handler(msgDump, c.dataHandler)
		go c.heartCheck()
		return
	}
	time.AfterFunc(2*time.Second, c.reconnect)

}
func (c *Client) handler(ch <-chan *PacketData, fn func(p *PacketData)) {
	for {
		select {
		case p := <-ch:
			go fn(p)
		case <-c.closeChan:
			log.Printf("客户端连接:[%s]【hander线程】退出", c.id)
			return
		}
	}
}

func (c *Client) heartCheck() {
	ticker := time.NewTicker(5 * time.Second)
	for {
		select {
		case <-c.closeChan:
			log.Printf("客户端连接:[%s]【heartCheck线程】退出", c.id)
			return
		case <-ticker.C:
			now := time.Now().Unix()
			cnow := c.timestamp
			//log.Println(now, cnow)
			if cnow+15 < now {
				log.Printf("客户端连接:[%s]【heartCheck线程】退出", c.id)
				c.Close()
				time.Sleep(500 * time.Millisecond)
				return
			}
		}
	}
}
